/* 
  ECMAScript 是一种脚本语言。javascript是ES扩展版本 
  浏览器中的javascript = ES + DOM + BOM
  node中的javascript = ES + fs + net
*/



/* 
let、const、var都是用来变量声明
let、const 块级作用域， var是函数作用域、全局作用域
let、const没有变量提升，var则有变量提升（可以先使用，再声明） 
let、const不允许重复声明
const声明一个常量,一旦声明就要赋值。当值为一个基本数据类型时不能修改,
当值为引用类型时，不能修改引用，可以修改引用对象得成员属性
**最佳实践：不用var，主用const，配合let**
*/


// let i = 0;
for (let i = 0; i < 3; i++) {
  // let i = 'bo';
  // console.log(i)
}
// console.log(i)


/* 
  数组结构
*/
const arr = [100, 200, 300];
const [bar] = arr;


/* 对象结构 */
const obj = {
  name: 'ty',
  age: 28
}

const {
  name
} = obj;
const {
  log
} = console;


/* 模板字符串 */
/* 模板字符串作为参数传递时，静态得字段串当作函数得第一个参数string[]传递，每多一个动态的字符串就会多一个实参 */
const box = 1;
const pox = 2;
const str = `hello es2015 \`strig\``;
// log(str);
function myTagFn(...str) {
  log(str);
  // log(strings, box, pox)
}

//myTagFn`test${box}  32424  2 ${pox} 342342`


/* 
  字符串扩展 
  includes()      判断字符串是否包含指定内容，包含返回true、否则false
  startsWith()    判断指定内容是否在字符串开头，返回结果同上
  endsWith        判断。。。。。。。。。。结尾，。。。。。。
*/
const message = `Uncaught ReferenceError: box is not defined`;
log(
  // message.includes('box')          // true
  // message.startsWith('Uncaught')   // true
  // message.endsWith('defined')      // true
)


/* 
  参数默认值 
  ES5 通过判断参数类型，进行短路运算。
  ES6 直接在参数列表中声明。并且当参数===undefined时，才会取默认值
  带默认值得参数需要在参数列表最后
*/

function fn1(param = 1) {
  console.log(param)
}
// fn1(23);

/* 
  剩余参数---展开数组
  ES5 用arguments
  ES6 用...rest, rest只能为最后一个参数
*/
function fn2(...rest) {
  console.log(rest)
}
// fn2(1, 23, 2, 3, 124, 2312, 14);

// console.log.apply(console, arr);



/* 
  箭头函数
  顾名思义就是用(=>)的方式定义函数
  1、函数体内的this对象，就是定义时所在的对象，而不是使用时所在的对象。
  2、不可以当作构造函数
  3、不可以使用arguments对象，该对象在函数体内不存在。如果要用，可以用 rest 参数代替。
  4、不可以使用yield命令，因此箭头函数不能用作 Generator 函数。

  不适合使用箭头函数的场景
  1、定义对象的方法，且该方法内部包括this。
  2、需要动态this的时候，也不应使用箭头函数（节点绑定事件时）。

*/
const fn3 = n => n;
// console.log(10)

const obj1 = {
  name: 'obj',
  /* fn1: () => {
    // 此时的this就是当前对象{}
    // es6不建议这样声明函数。定义对象的方法，且该方法内部包括this。
    console.log(this )
  } */
  fn1: function () {
    setTimeout(() => {
      console.log(this)
    })
  }
}
// obj1.fn1()



/* 
  对象扩充 
  Object.assign()  方法用于对象的合并，将源对象（source）的所有可枚举属性，复制到目标对象（target）
  Object.is()      比较两个值是否相等 NaN === NaN, -0 !== +0
*/

const target = {
  a: 1
};
const source1 = {
  b: 2
};
const source2 = {
  c: 3
};
Object.assign(target, source1, source2);

/* console.log(Object.is(NaN, NaN)) 
console.log(Object.is(-0, +0))  */


/* 
  Proxy 对象代理
  **Proxy\defindProperty对比**
  Proxy优势：
  Proxy 可以直接监听对象而非属性；
  Proxy 可以直接监听数组的变化；
  Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的；
  Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改；
  Proxy 作为新标准将受到浏览器厂商重点持续的性能优化，也就是传说中的新标准的性能红利；
  defindProperty优势：
  兼容性好
*/

const person = {
  name: 'ty',
  age: 18
}

const personProxy = new Proxy(person, {
  get(target, property) {
    return property in target ? target[property] : '冒得这个属性';
  },

  set(target, property, value) {
    if (property == 'age') {
      if (!Number.isInteger(value)) {
        throw new Error('错误类型！');
      }
      if (value < 18) {
        console.log('你还未成年！！')
      }
    }
  },

  has(target, propKey) {
    // return target[propKey]
    // console.log(propKey)
    return true
  },

  deleteProperty(target, propKey) {
    delete target[propKey];
  }
})

// console.log(personProxy.name)
// console.log(personProxy.box)
// personProxy.age = '12'

// console.log('name' in personProxy)
delete personProxy.name;
// console.log(personProxy)


/* Reflect  提供统一一套用于操作对象得API*/


/* Promise */

/* Class类 */
class Person {
  // 构造函数
  constructor(name) {
    this.name = name;
  }

  say() {
    console.log(`hello ${this.name}`)
  }

  // 静态方法
  static create(name) {
    // 静态方法得this指向得是Person
    return new Person(name);
  }
}

// const p = new Person('ty');
// p.say();

// const p1 = Person.create('yt');
// p1.say();


/* 
  类的继承 
*/
class Student extends Person {
  constructor(name, number) {
    // 调用父级的构造函数。并且super前面不能有this关键字
    super(name);
    this.number = number;
  }

  hello() {
    // 这个位置可以调this的方法，也可以调super的方法
    // this.say();
    super.say();
  }
}

const student = new Student('taoyang', 20);
//  student.say();


/* 
  Set 类似于数组，但是成员的值都是唯一的，没有重复的值。
  Map JavaScript 的对象（Object），本质上是键值对的集合（Hash 结构），但是传统上只能用字符串当作键
*/
const map = new Map();


/* Symbol  最主要的作用就是为对象添加独一无二的属性名 */
const obj3 = {
  [Symbol.toStringTag]: 'x object'
}
// console.log(obj3)
// console.log(obj3.toString())


/* 
  for...of循环 可以终止遍历
*/
const arr3 = [10, 20, 30, 40, 50, 60, 70];

// forEach 不能终止遍历
arr3.forEach(item => {
  // console.log(item)
  if (item > 30) {
    return
  }
})

for (let item of arr3) {
  //console.log(item);
  if (item > 30) {
    break;
  }
}


/* 
  Iterable  对外提供统一遍历接口
*/
const obj4 = {
  store: ['苹果', '香蕉', '橙子'],
  life: ['吃饭', '睡觉', '打豆豆'],
  [Symbol.iterator]: function () {
    const arr = [...this.store, ...this.life];
    let index = 0;
    return {
      next: function () {
        return {
          done: index >= arr.length,
          value: arr[index++]
        }
      }
    }
  }
}

// for (let i of obj4) {
//   console.log(i)
// }


/* 
generator 惰性执行

*/

function* fn4() {
  console.log('ty');
  return 100;
}
// const result = fn4();
// console.log(result.next())

// 发号器案例
function* createMake() {
  let id = 0;
  while (true) { // 这里不会死循环，因为generator遇到yield就会停止运行，等待下一个next方法
    yield ++id;
  }
}

const make = createMake();

// console.log(make.next().value);
// console.log(make.next().value);
// console.log(make.next().value);
// console.log(make.next().value);


/* 通过generator 优化 Iterable*/
const obj5 = {
  store: ['苹果', '香蕉', '橙子'],
  life: ['吃饭', '睡觉', '打豆豆'],
  [Symbol.iterator]: function* () {
    const arr = [...this.store, ...this.life];
    for (let item of arr) {
      yield item
    }
  }
}

// for (let item of obj5) {
//   console.log(item)
// }

/* ES modules */

/* ES 2016  includes   指数运算 */

/* 
  ES 2017 
*/